Skip to content

客诉项目面试准备

针对面试内容,做一次项目复习。

项目介绍

  • 面试官,你好,我叫 XXX ,非常荣幸有机会参加这次面试,我个人是从20年本科软件工程毕业,毕业后从事 于 Java 开发工作,目前已经 4 年的一个经历。
  • 我入职的第一家公司主要做的是制造业相关一些项目,然后我最近的这个前公司主要做的是物流行业相关的一些项目,然后我最近做的这个项目是客诉平台,主要使用的技术栈有SpringCloud、Nacos、Redis、RocketMQ、MySQL;
  • 这个项目主要解决的是用户关于金额的一些纠纷或者运单的一些诉讼,然后会通过该平台去进行申诉处理,客服根据平台的客诉信息进行处理并解决纠纷。
  • 这个平台主要会涉及到客诉单的一个生成,同时相关的还有催收、申诉、申请单的相关内容,我这边的主要工作是减少客服的一些人为操作,将一下线下可行的操作放到线上去做,主要还是做一些申请单的业务开发工作;
  • 同时个人主要负责的是相关业务开发和部分接口优化以及需求迭代,中间也遇到过Redis、接口优化、SQL 调优相关、服务拆分、大表拆分的一些工作。

随性一点讲,不要特意准备,讲你会的技术栈,大概做了什么工作都讲一下,然后跟面试官沟通的语气去聊。

包括当时遇到的一些问题和自己如何解决的,讲一下。

  • 中间建议讲详细一点

  • PM 和 TL

    • 业务方提供给产品,产品完成 PRD 的出版,研发、产品、业务方拉会沟通交流,→ 需求开发
    • 入职
      • 1 -2 天环境搭建
      • 导师制讲解
      • Dev 跑一下流程,两周熟悉流程
  • 讲一下这个项目的作用,主要的作用;中间遇到的一些问题,

这里有点问题

参考: https://www.bilibili.com/video/BV1iH4y1V7G5

争取能说一个 7 分钟左右

放后面去准备一下

项目难点

这里说一下服务拆分

服务拆分

  • 服务拆分
  • 大表拆分
  • 设计模式

服务拆分

  • 业务需求
    • 当时考虑到服务拆分,主要还是从业务迭代方面考虑,一方面服务拆分后,单个服务的故障风险会更小,且有利于职责划分和后期迭代;
    • 当时拆分一方面是围绕了单一职责的原则去进行划分,将客诉平台拆分为客诉平台,催收平台,申请单平台三个独立的微服务
    • 当时主要是通过横向拆分的方式,通过不同的业务功能,进行拆分
  • 难点
    • 拆分难点的话,首先当时是考虑需要围绕单一职责进行,第一需要做的是进行业务梳理,梳理出主要业务,以及模块边界。
    • 说一下当时业务场景
      • 客诉平台主要有
  • 服务测试
    • 一般是先拆分后,在单个服务进行单元测试,保证服务能够正常跑,之后进行集成测试,多个服务间的调用测试,确保通信正常,之后是端到端测试,模拟真实情况下用户的使用(链路测试,可以说这个步骤是测试人员做的)

看一下这个: https://www.cnblogs.com/syjkfind/p/13626319.html

这里能讲的东西还挺多的,不管是设计模式,还是对于微服务的理解,还有业务方面的内容

参考: https://www.bilibili.com/video/BV1gH4y1R7TM

  • 挑战

  • 解决方案

  • 项目中的产出和遇到的挑战

  • 对项目中技术、业务,团队协作等方面的思考

  • 项目中遇到的挑战

  • 项目中的亮点和难点

  • 你在项目中的产出

遇到的问题

  • 1、数据一致性问题
    • 如何确保各个服务之间的数据同步和一致性
    • 事件驱动架构
  • 2、服务依赖关系
    • DDD 思想,服务边界划分
    • API 网关管理服务之间的依赖关系,对外提供统一的接口
  • 3、部署和监控
    • CI/CD 流水线、监控体系

参考: https://www.bilibili.com/video/BV1kY411E7bb

微服务拆分原则

  • 1、考虑团队人员结构
  • 2、服务粒度适中(先粗粒度后细粒度,先少后多
  • 3、单一职责原则(高内聚和低耦合)
  • 4、服务自治原则
    • 尽量不强依赖于其他模块的服务,而是通过消息队列来进行解耦,提升服务的稳定性
    • 服务尽量通过标准的 restful 接口对外提供
    • 接口在设计的时候尽量设置为幂等
    • 单个服务可独立的进行开发、测试和上线
  • 5、服务之间轻量级通讯协议
  • 6、服务拆分与日常业务需求并行进行、考虑迁移方案与回滚方案
  • 7、服务拆分避免循环依赖
    • 一种是双向依赖,还有一种是环形依赖 ABC 形成 N 角环。循环依赖会加深服务间的耦合程度
    • 处理方案
      • 1、共同依赖的功能,下沉到第三方服务,协商由专门的团队去维护
      • 2、在服务的上层引入一个聚合层,互相依赖的这部分功能,可以通过聚合层去调用不同的服务,从而进行一个业务方面的组装
      • 3、采用服务异步化的方式,服务之间使用消息队列的方式进行解耦
  • 8、分迭代逐步拆分、持续演进

一般来说,微服务的数量 ≈服务端开发人数/3。服务模块维护:1 个高级 + 2 个中级

水平拆表这里有点理解问题,得看一下:

主键 ID

分库分表之后,id主键如何处理

参考: https://www.bilibili.com/video/BV1Ja4y1K7zG

image.png

雪花算法

  • 缺点:强依赖机器时钟

image.png

美团 Leaf 算法

  • 优化了雪花算法的缺点

参考: https://www.bilibili.com/video/BV1wB4y1i7qP

https://www.bilibili.com/video/BV1wB4y1i7qP

image.png

  • UUID 并发量较小,没有顺序
  • 数据库自增 ID
    • 分布式环境单独维护一个表(单个数据库)
    • 不能满足三高

美团的 Leaf 算法通过两种模式(号段模式和 Snowflake 模式)提供了高效的分布式唯一ID生成方案。

  • 号段模式
    • 基于数据库,取号段
  • Snowflake 模式
    • 基于 Twitter 开源的 Snowflake 算法的分布式ID生成方案

为什么要进行服务拆分: http://svip.iocoder.cn/Dubbo/Interview#为什么要将系统进行拆分?

系统上线的耦合程度太高,每次发布可能每次上线都要做很多的检查,很多异常问题的处理,简直是又麻烦又痛苦

如果是那种代码量多达几十万行的中大型项目,团队里有几十个人,那么如果不拆分系统,开发效率极其低下,问题很多。但是拆分系统之后,每个人就负责自己的一小部分就好了,可以随便玩儿随便弄。

分布式系统拆分之后,可以大幅度提升复杂系统大型团队的开发效率。

与之相对的是:系统拆分成分布式系统之后,大量的分布式系统面临的问题也是接踵而来,

接口优化

  • 1、看你这里是说使用到了接口优化,说一下你相关的一些理解,以及怎么做的
    • 发送了什么,当时的业务场景,为什么要优化
    • 怎么优化的,对于优化的理解
    • 优化的效果,总结复盘。
  • 2、Redis 优化

写一份关于接口优化的文章

  • 线程池的状态
  • 非核心线程如何回收的、阻塞队列
    • 并发安全性

如何找到接口慢的现象,觉得这里也可以说一些东西

接口优化方法总结:

  • 1、代码的问题
    • 循环体内的IO、远程调用,改为循环外去重后批量执行,避免重复发起调用
  • 2、数据库查询问题
    • 数据库慢查询,优化SQL、索引
  • 3、频繁查询
    • 基础的、频繁查询的方法,可以把执行结果放到缓存
  • 4、串行改并行
    • 串行的远程调用可以改为并行。(慎用,请求高峰期会造成内存暴涨,同时也可能导致上下文丢失)
  • 5、异步,消息队列
    • 非主流程的方法,比如发消息通知、增删会员积分,改为发消息到队列然后异步消费

这个双刃剑的东西不好讲: https://developer.aliyun.com/article/1413669

  • 串行改并行可能会出现什么问题,当时是怎么考虑的,具体怎么做的

感觉还是围绕接口优化这个点去讲,然后也讲一下接口问题排查,这个东西细讲也是可以讲挺多东西的,整理一下进行输出。

多线程这里主要是能够讲述一下接口优化的几种方式,以及怎么使用的,然后还有线程池的参数配置重要程度会高一些。

Futrue

Com

如果要讲线程的话,深度有点挺难搞的,建议从广度方面先掌握一下


Reidis 问题

写一份关于 Redis 使用缓存的文章,或者在之前的基础上改进一下相关内容

写一份关于分布式锁的文章

  • Redis 分布式锁

  • 业务问题:

  • 缓存优化

    • 接口优化的话一般也可以考虑空间换时间的策略,当前是排查到获取客诉类型下拉框维护数据的时间耗时较大,因此当时这个是树结构
    • 这里存放的是树结构, Hash + Set 的方式(具体可以看一下树结构是怎么样的结构)
    • 获取通过 ID 获取

分布式锁问题这里需要再看一下

08 面试煎熬成蛋_Redis#大 Key 问题

JVM 排查

JVM 线上问题: https://javaguide.cn/java/jvm/jvm-in-action.html

文档有点勉强

image.png

视频课程学习笔记_JVM_垃圾回收机制

image.png

关闭自定义适应大小策略: https://blog.csdn.net/u014263388/article/details/105617350

分布式事务

SpringCloud_分布式事务

他说的这个封装感觉就是指的使用Seata注解进行事务管理之类的,不封装的话,我刚刚看到一种方案是通过 MQ + 补偿来进行的,比如服务A 执行完本地事务后,然后发送一条消息,服务B进行监听,监听后执行本地事务,同时通过幂等行保证如果服务A 重复发送能够不影响业务操作;如果服务B 的本地事务执行失败的话,放入到 死信队列,进行重试;同时针对服务A,通过手动补偿机制 tcc 的方式来进行回滚/确认已经完成的本地事务,确保最终一致性。

简历内容

参考: https://www.bilibili.com/video/BV1Qr421V7Cj

人员配比和环境配置

做一件事情,尝试

image.png


速通

  • 分布式事务问题
  • 分布式锁
  • Redis 缓存使用
  • 接口优化
  • JVM 排查
    • JVM 默认 Parallel、Parallel Old
    • 关闭自适应大小策略
  • 服务拆分
    • 服务拆分
    • 设计模式
      • 模板设计模式
      • 策略设计模式
    • 大表设计
      • 水平分表
      • ID 设计

模拟问答

  1. 你能详细介绍一下这个集中处理司机与货主间交易纠纷的平台是如何工作的?
  2. 在处理客诉单的流程中,客服部门和领导复审的具体职责是什么?
  3. 消息队列和同步接口调用在平台中具体是如何实现高效交互的?
  4. 财务方面,涉及垫付的催收和申诉单是如何生成和处理的?
  5. 平台在迭代过程中,你们采取了哪些措施来提高客服处理效率和整体服务质量?

问题:你能详细介绍一下这个集中处理司机与货主间交易纠纷的平台是如何工作的?

  • 在我们的平台中,我们主要面对的用户是司机和货主,主要的客诉数据来源的话,一般是他们手动在平台提交客诉信息,或者他们联系客服,然后客服针对货主和司机之间的纠纷,创建对应的客诉单信息。
  • 同时围绕一些其他的纠纷,其实不一定只是用户之间的纠纷,比如像一些用户反馈某个服务有问题,或者说系统结算金额不对之类,这些事情走的都是客诉。
  • 客诉系统,创建客诉的时候会判断是否还有未结算状态的客诉单,如果有,需要先处理上一次客诉内容再进行重开操作。
  • 如果是货主和司机之间的纠纷,创建客诉单的同时会进行冻结运单操作,冻结运单后,会进行客诉单数据落库操作,客诉单数据落库后,会发送一条MQ数据进行异步ES封装查询数据的操作,之后还有比如发送消息给工单部门,工单根据客诉类型和客诉单ID,根据规则模板引擎生成一些申请单的模板,这个是同步调用的一个接口,工单部门会返回一个 true/false 标识,生成申请单模板后,之后会进行插入记录表的操作,同时还会插入一条外呼表记录,这个外呼表的作用,我们当时是后台会有一个定时任务,然后定时任务会将未呼叫的客诉信息通过 MQ 方式发给外呼部门,外呼会通过语言或者短信的方式提醒用户客诉单信息已经创建成功。

问题:在处理客诉单的流程中,客服部门和领导复审的具体职责是什么?

  • 客服部门的话,我们当时是创建客诉单后,会有定时任务按照一定的分配均等份分为客服,其中如果是重开的客诉申请,我们会优先分配给上一次处理过这个客诉信息的客服。【状态:待领取,已领取,审核中,审核完成,已办结】
  • 客服会登录我们的客诉平台界面,然后进入到客诉界面,查询界面的数据会从 ES 中进行查询,如果需要处理某一个客诉单,需要进行到详情界面,这个界面我们的当时的话,主要是会获取到 ES 中的 客诉单ID,然后根据客诉单 ID 进行查询,这个界面当时的话是采用的同步调用的方式,一方面是首先查询客诉单数据库的数据,这里是查询客诉单信息和记录表操作信息,另外还会调用用户中心服务和货运服务的接口,查询用户、货物的信息,同时还会查询客诉类型的信息,除外之外,会有一些申请按钮。客服会根据实际的客诉情况,进行不同的操作,并进行不同的按钮操作,一般的话,会有:【退押金、退佣金、禁止发货、拉黑、垫付、办结】等操作。
  • 其实我们一般的工作,客诉就可以进行处理了,但是如果涉及到一定金额的,会需要进行审核操作,这个审核操作是客服领导进行操作的。

问题:消息队列和同步接口调用在平台中具体是如何实现高效交互的?

  • 我们当时主要还是考虑到是否需要同步反馈或者耗时操作,我们一般会采用MQ 进行解耦一些复杂业务,同时如果MQ 发送失败的话,我们会通过 定时 任务去进行补偿。
  • 同步调用的话,我们主要使用的 OpenFeign 的 远程调用接口,通过 OpenFeign 去进行同步调用。

问题: 财务方面,涉及垫付的催收和申诉单是如何生成和处理的?

  • 其实不管是催收还是申诉,仍然是基于客诉单的客服人员操作进行后续处理的;
  • 这里我大概说一下一个业务场景吧,司机如果接单的话,需要垫付押金进行派送(这个押金是司机给货主的,货主收到货物后,会退货押金给司机),交易完成后,会退还押金,会获取到运货报酬,平台会收取部门报酬佣金。
  • 当时如果说交易完成后,货主不进行退还押金的操作,司机可以提起客诉。
  • 客服人员一般会通过垫付的操作,将押金和报酬给司机,然后产生一个催收单。
  • 这个催收的目的,其实就是需要让货主进行退还押金。
  • 申诉一般是,如果货主或者司机对于客诉判定结果不满意,提起申诉反馈,客服这边也是会进行一个人工处理。

问题:平台在迭代过程中,你们采取了哪些措施来提高客服处理效率和整体服务质量?

  • 我们平台其实主要的一些工作量,也是围绕减少不必要的人工处理,通过将一些线下能够跑通的流程,搬到线上,一般来说,会是 业务方提出,然后 PM 项目经理 和 TL 技术领导 以及开发人员进行会议讨论。然后将一些流程线上化操作。
  • 这个过程中,其实也有做许多工作,比如当时的话,是将详情界面进行一个查询优化,当时是通过串行改并行的方式,同时设置了一个自定义线程池,自定义参数并在实际调试中不断优化,然后将一些维护数据,通过空间换时间的方式,采用了缓存 Redis 存入了当时的客诉类型。
  • 另外,针对其中记录表数据过大,当时进行了一个水平拆分的操作,当时主要的操作难点是:一个是数据迁移,一个是拆分设计。
  • 同时呢,为了后续的职责划分,和业务迭代,进行了服务拆分的操作。

  1. 在这个项目中,SpringBoot + MyBatis + SpringCloud + ES + RocketMQ + Redis + MySQL 的技术栈是如何协同工作的?
  2. 你们是如何实现客诉平台、催收平台和申请单平台的微服务拆分的?
  3. 你们在优化慢 SQL 时,采用了哪些具体的调优策略?
  4. 在服务调用的并行处理方面,具体是如何将调用时间从10s压缩到2s的?
  5. 在处理分布式事务时,RocketMQ 半事务消息是如何实现回滚逻辑的?
  6. 针对 Redis 内存溢出问题,你们采取了哪些优化策略?
  7. 针对高峰期 TP99 耗时变高的问题,你们是如何分析和解决 Full GC 问题的?

问题:在这个项目中,SpringBoot + MyBatis + SpringCloud + ES + RocketMQ + Redis + MySQL 的技术栈是如何协同工作的?

问题:你们是如何实现客诉平台、催收平台和申请单平台的微服务拆分的?

  • 服务拆分中

  • 1、考虑团队人员结构

  • 2、服务粒度适中(先粗粒度后细粒度,先少后多

  • 3、单一职责原则(高内聚和低耦合)

  • 4、服务自治原则

    • 尽量不强依赖于其他模块的服务,而是通过消息队列来进行解耦,提升服务的稳定性
    • 服务尽量通过标准的 restful 接口对外提供
    • 接口在设计的时候尽量设置为幂等
    • 单个服务可独立的进行开发、测试和上线
  • 5、服务之间轻量级通讯协议

  • 6、服务拆分与日常业务需求并行进行、考虑迁移方案与回滚方案

  • 7、服务拆分避免循环依赖

    • 一种是双向依赖,还有一种是环形依赖 ABC 形成 N 角环。循环依赖会加深服务间的耦合程度
    • 处理方案
      • 1、共同依赖的功能,下沉到第三方服务,协商由专门的团队去维护
      • 2、在服务的上层引入一个聚合层,互相依赖的这部分功能,可以通过聚合层去调用不同的服务,从而进行一个业务方面的组装
      • 3、采用服务异步化的方式,服务之间使用消息队列的方式进行解耦
  • 8、分迭代逐步拆分、持续演进

分析每个服务的职责和边界,将其独立成不同的微服务。使用 SpringCloud 的 Eureka 进行服务注册与发现,确保各服务之间的调用通过负载均衡实现。通过消息队列(如 RocketMQ)进行异步通信,减少服务之间的直接依赖。同时,制定统一的 API 接口规范,确保各服务之间的数据交互一致性。

  1. 你在需求分析与开发过程中,如何确保客诉单的创建与重开、催收单和申请单等关键功能的实现?
  2. 在微服务拆分过程中,遇到过哪些挑战?是如何克服的?
  3. 在日常监控和优化慢 SQL 的过程中,你们使用了哪些工具和方法?
  4. 你是如何通过并行处理和缓存、异步处理来解决服务调用耗时过大的问题的?
  5. 你能详细描述一下针对分布式事务问题所编写的回滚逻辑吗?
  6. 针对 Redis 内存溢出问题的排查过程是怎样的?采取了哪些措施来优化内存使用策略?
  7. 在解决创单接口高峰期频繁 Full GC 问题时,你们采取了哪些具体的步骤和措施来提升系统稳定性和性能?

问题:你是如何通过并行处理和缓存、异步处理来解决服务调用耗时过大的问题的?

  • 将耗时操作分解为多个独立的任务,使用多线程并行处理,提高总的处理效率。使用 Redis 缓存频繁访问的数据,减少数据库和远程服务的调用次数。对于非实时的任务,采用异步处理,使用 CompletableFuture 或消息队列进行异步调用和回调。监控和调整线程池参数,优化并发任务的执行。

问题:你能详细描述一下针对分布式事务问题所编写的回滚逻辑吗?

  • 在 RocketMQ 半事务消息的 prepare 阶段,记录本地事务的初始状态,发送预处理消息。执行本地事务操作,确保所有操作成功后,提交消息。如果本地事务失败,则回滚本地操作,并标记事务为失败。在消息消费者侧,根据本地事务状态决定是否提交或回滚消息,确保分布式事务的一致性。

相关扩展

日常工作:

1、客诉数据分析和报表 2、客户反馈表单收集和评价分析 3、补偿发放(因服务问题提供一定的补偿) 4、黑名单管理 5、定时备份客诉数据库数据以及容灾恢复

分布式锁

  • 我如果讲的话就围绕锁的原子性去讲,从单JVM 进程使用 synchronized 以及 跨 JVM 进程,引入 Redis 分布式锁/ZK/或者数据库乐观、悲观,保证对数据状态修改的一个原子性操作

  • 然后再讲一下自己对于锁的理解,包括 redisson 组件的可重入、watchdog 监听,然后再扩展讲一些东西,或者讲讲实际代码

  • 单例使用

  • 生命周期的使用


模拟问答

  • ES 问答

  • MySQL 的性能优化

    • 1、数据库和表的优化,选择合适的字段和属性,尽量遵循范式原则,尽量选择合适索引
    • 2、业务方面,违反范式
    • 3、索引优化,选择区分度比较高的列,建立唯一索引和联合索引等
    • SQL 语句的优化
      • 写 SQL 的时候避免索引失效
      • 尽量避免使用子查询,而使用连接查询
      • 修改语句中经常作为判断添加索引,避免行锁
    • 架构优化
      • 集群优化
      • 主从集群,读写分离
      • 读写多的表,进行水平分表或者垂直分表
      • 建立缓存